// Copyright (c), 2006, adopus consulting GmbH Switzerland, all rights reserved.
package com.purej.ui;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingWorker;
import com.purej.ui.Task.Publication;

/**
 * The abstract class for long-running background tasks that should be executed
 * using a SwingWorker.
 *
 * @author Stefan Mueller
 */
public abstract class Task<T> extends SwingWorker<T, Publication> {
  private long _startTime;
  private boolean _interrupted;
  private List<TaskListener> _listeners;

  /**
   * Inner class for intermediate publications.
   */
  private static enum PublicationType {
    STATUS, ERROR
  }

  /**
   * Inner class for intermediate publications.
   */
  public static final class Publication {
    private final PublicationType _type;
    private final String _data;

    private Publication(PublicationType type, String data) {
      _type = type;
      _data = data;
    }
  }

  /**
   * Schedules this Task for execution on a <i>worker</i> thread.
   * This method stores all given listeners to be notified and
   * calls the {@link #execute()} method.
   */
  public void execute(List<TaskListener> listeners) {
    _listeners = new ArrayList<TaskListener>(listeners);
    for (TaskListener listener : _listeners) {
      listener.taskStarted(this);
    }
    super.execute();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected final T doInBackground() {
    _startTime = System.currentTimeMillis();
    try {
      return doWork();
    }
    catch (Throwable t) {
      t.printStackTrace();
      publishError("The task execution failed with an error!");
    }
    return null;
  }

  /**
   * Executes the real task work in a the background thread.
   * Should not throw exceptions.
   */
  protected abstract T doWork();

  /**
   * {@inheritDoc}
   */
  @Override
  protected final void done() {
    String message;
    if (isInterrupted()) {
      message = "Task execution interrupted!";
    }
    else {
      long endTime = System.currentTimeMillis();
      DecimalFormat numberFormat = new DecimalFormat();
      numberFormat.setMinimumFractionDigits(1);
      numberFormat.setMaximumFractionDigits(1);
      String seconds = numberFormat.format((endTime - _startTime) / 1000d);
      message = "Task execution finished in " + seconds + " seconds";
    }

    // Notify the end of the task & send the info message:
    for (TaskListener listener : _listeners) {
      listener.taskFinished(this, message);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected final void process(List<Publication> publications) {
    // Iterate over all publications:
    String lastStatusMessage = null;
    List<String> errorMessages = new ArrayList<String>(3);
    for (Publication publication : publications) {
      if (publication._type == PublicationType.STATUS) {
        lastStatusMessage = publication._data;
      }
      else {
        errorMessages.add(publication._data);
      }
    }

    // Now notify latest status message and all error messages:
    for (TaskListener listener : _listeners) {
      if (lastStatusMessage != null) {
        listener.taskStatusMessage(this, lastStatusMessage);
      }
      for (String errorMessage : errorMessages) {
        listener.taskErrorMessage(this, errorMessage);
      }
    }
  }

  /**
   * Method that can be used by background task-executions to publish
   * a status message (usually to be displayed in the status-bar).
   *
   * @param message the message to be published
   */
  protected final void publishStatusMessage(String message) {
    publish(new Publication(PublicationType.STATUS, message));
  }

  /**
   * Method that can be used by background task-executions to publish
   * an error message (usually to be displayed as a application message).
   *
   * @param message the message to be published
   */
  protected final void publishError(String message) {
    publish(new Publication(PublicationType.ERROR, message));
  }

  /**
   * Returns if this task is interruptable.
   *
   * @return if this task can be interrupted
   */
  public boolean isInterruptable() {
    // True by default, might be overwritten by subclasses...
    return true;
  }

  /**
   * Interrupts this task as soon as possible.
   */
  public final void interrupt() {
    _interrupted = true;
  }

  /**
   * @return if interrupted
   */
  public final boolean isInterrupted() {
    return _interrupted;
  }
}
